﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/21 14:16:35
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using ImageK.Gui;
using ImageK.Measure;
using ImageK.Process;

namespace ImageK.Plugin
{
    /// <summary>
    /// This plugin implements the Edit/Crop and Image/Adjust/Size commands.
    /// </summary>
    public class Resizer:PlugIn
    {
        public const int IN_PLACE = 16, SCALE_T = 32;
        private static int newWidth;
        private static int newHeight;
        private static bool constrain = true;
        private bool averageWhenDownsizing = true;
        private static int interpolationMethod = ImageProcessor.BILINEAR;
        private string[] methods = ImageProcessor.getInterpolationMethods();
        private ArrayList fields, checkboxes;
        private double origWidth, origHeight;
        private bool sizeToHeight;

        public void run(string arg)
        {
			bool crop = arg.Equals("crop");
			ImagePlus imp = IJ.getImage();
			ImageProcessor ip = imp.getProcessor();
			Roi roi = imp.getRoi();
			int bitDepth = imp.getBitDepth();
			double min = ip.getMin();
			double max = ip.getMax();
			if (!imp.okToDeleteRoi())
				return;
			if ((roi == null || !roi.isArea()) && crop)
			{
				IJ.error(crop ? "Crop" : "Resize", "Area selection required");
				return;
			}
			if (!imp.Lock()) {
				IJ.log("<<Resizer: image is locked (" + imp + ")>>");
				return;
			}
			Rectangle r = ip.getRoi();
			origWidth = r.Width; ;
			origHeight = r.Height;
			sizeToHeight = false;
			bool restoreRoi = crop && roi != null && roi.getType() != Roi.RECTANGLE;
			if (roi != null)
			{
				//todo:
				// Rectangle b = roi.getBounds();
				// int w = ip.getWidth();
				// int h = ip.getHeight();
				// if (b.x < 0 || b.y < 0 || b.x + b.width > w || b.y + b.height > h)
				// {
				// 	ShapeRoi shape1 = new ShapeRoi(roi);
				// 	ShapeRoi shape2 = new ShapeRoi(new Roi(0, 0, w, h));
				// 	roi = shape2.and(shape1);
				// 	if (roi.getBounds().width == 0 || roi.getBounds().height == 0)
				// 	{
				// 		if (IJ.isMacro())
				// 			IJ.log("Selection is outside image");
				// 		else
				// 			throw new ArgumentException("Selection is outside image");
				// 	}
				// 	if (restoreRoi) imp.setRoi(roi);
				// }
			}
			int stackSize = imp.getStackSize();
			int z1 = imp.getStackSize();
			int t1 = 0;
			int z2 = 0, t2 = 0;
			int saveMethod = interpolationMethod;
			if (crop)
			{
				Rectangle bounds = roi.getBounds();
				newWidth = bounds.Width;
				newHeight = bounds.Height;
				interpolationMethod = ImageProcessor.NONE;
			}
			else
			{
				if (newWidth == 0 || newHeight == 0)
				{
					newWidth = (int)origWidth / 2;
					newHeight = (int)origHeight / 2;
				}
				if (constrain) newHeight = (int)(newWidth * (origHeight / origWidth));
				if (stackSize > 1)
				{
					newWidth = (int)origWidth;
					newHeight = (int)origHeight;
				}
				GenericDialog gd = new GenericDialog("Resize");
				gd.addNumericField("Width (pixels):", newWidth, 0);
				gd.addNumericField("Height (pixels):", newHeight, 0);
				if (imp.isHyperStack())
				{
					z1 = imp.getNSlices();
					t1 = imp.getNFrames();
				}
				if (z1 == stackSize)
					gd.addNumericField("Depth (images):", z1, 0);
				else if (z1 > 1 && z1 < stackSize)
					gd.addNumericField("Depth (slices):", z1, 0);
				if (t1 > 1)
					gd.addNumericField("Time (frames):", t1, 0);
				gd.addCheckbox("Constrain aspect ratio", constrain);
				gd.addCheckbox("Average when downsizing", averageWhenDownsizing);
				gd.addChoice("Interpolation:", methods, methods[interpolationMethod]);
				fields = gd.getNumericFields();
				// if (!IJ.macroRunning())
				// {
				// 	for (int i = 0; i < 2; i++)
				// 		((TextField)fields.elementAt(i)).addTextListener(this);
				// }
				// checkboxes = gd.getCheckboxes();
				// if (!IJ.macroRunning())
				// 	((Checkbox)checkboxes.elementAt(0)).addItemListener(this);
				gd.showDialog();
				if (gd.wasCanceled())
				{
					imp.Unlock();
					return;
				}
				newWidth = (int)gd.getNextNumber();
				newHeight = (int)gd.getNextNumber();
				if (z1 == stackSize || (z1 > 1 && z1 < stackSize))
					z2 = (int)gd.getNextNumber();
				if (t1 > 1)
					t2 = (int)gd.getNextNumber();
				if (gd.invalidNumber())
				{
					IJ.error("Width or height are invalid.");
					imp.Unlock();
					return;
				}
				constrain = gd.getNextBoolean();
				averageWhenDownsizing = gd.getNextBoolean();
				interpolationMethod = gd.getNextChoiceIndex();
				if (constrain && newWidth == 0)
					sizeToHeight = true;
				if (newWidth <= 0.0 && !constrain) newWidth = 50;
				if (newHeight <= 0.0) newHeight = 50;
			}

			if (!crop && constrain)
			{
				if (sizeToHeight)
					newWidth = (int)Math.Round(newHeight * (origWidth / origHeight));
				else
					newHeight = (int)Math.Round(newWidth * (origHeight / origWidth));
			}
			ip.setInterpolationMethod(interpolationMethod);
			Undo.setup(crop ? Undo.TRANSFORM : Undo.TYPE_CONVERSION, imp);
			
			if (roi != null || newWidth != origWidth || newHeight != origHeight)
			{
				try
				{
					StackProcessor sp = new StackProcessor(imp.getStack(), ip);
					ImageStack s2 = sp.resize(newWidth, newHeight, averageWhenDownsizing);
					int newSize = s2.getSize();
					if (s2.getWidth() > 0 && newSize > 0)
					{
						//todo:
						if (restoreRoi)
							imp.deleteRoi();
						Calibration cal = imp.getCalibration();
						if (cal.scaled())
						{
							cal.pixelWidth *= origWidth / newWidth;
							cal.pixelHeight *= origHeight / newHeight;
						}
						if (crop && roi != null && (cal.xOrigin != 0.0 || cal.yOrigin != 0.0))
						{
							cal.xOrigin -= roi.getBounds().X;
							cal.yOrigin -= roi.getBounds().Y;
						}
						imp.setStack(null, s2);
						if (crop && roi != null)
						{
							// Overlay overlay = imp.getOverlay();
							// if (overlay != null && !imp.getHideOverlay())
							// {
							// 	Overlay overlay2 = overlay.crop(roi.getBounds());
							// 	imp.setOverlay(overlay2);
							// }
						}
						else
						{
							Overlay overlay = imp.getOverlay();
                            if (overlay != null && !imp.getHideOverlay())
                            {
								//todo:
                                // imp.setOverlay(overlay.scale(newWidth / origWidth, newHeight / origHeight));
							}
							else
								imp.setOverlay(null);
						}
						if (restoreRoi && roi != null)
						{
							// roi.setLocation(0, 0);
							// imp.setRoi(roi);
							// imp.draw();
						}
					}
					if (stackSize > 1 && newSize < stackSize)
						IJ.error("ImageJ ran out of memory causing \nthe last " + (stackSize - newSize) + " slices to be lost.");
				}
				catch (OutOfMemoryException o)
				{
					IJ.outOfMemory("Resize");
				}
				imp.changes = true;
				if (crop)
					interpolationMethod = saveMethod;
			}
			
			ImagePlus imp2 = null;
			if (z2 > 0 && z2 != z1)
				imp2 = zScale(imp, z2, interpolationMethod + IN_PLACE);
			if (t2 > 0 && t2 != t1)
				imp2 = zScale(imp2 != null ? imp2 : imp, t2, interpolationMethod + IN_PLACE + SCALE_T);
			imp.Unlock();
			if (imp2 != null && imp2 != imp)
			{
				imp.changes = false;
				//todo:
				// imp.close();
				// imp2.show();
			}
			else if (crop && (bitDepth == 16 || bitDepth == 32))
			{
				imp.setDisplayRange(min, max);
				imp.updateAndDraw();
			}
			// Scaler.record(imp, newWidth, newHeight, 1, interpolationMethod);
		}

        public ImagePlus zScale(ImagePlus imp, int newDepth, int interpolationMethod)
        {
            ImagePlus imp2 = null;
            if (imp.isHyperStack())
            {
                // imp2 = zScaleHyperstack(imp, newDepth, interpolationMethod);
			}
            else
            {
                bool inPlace = (interpolationMethod & IN_PLACE) != 0;
                interpolationMethod = interpolationMethod & 15;
                int stackSize = imp.getStackSize();
                int bitDepth = imp.getBitDepth();
                imp2 = resizeZ(imp, newDepth, interpolationMethod);
                if (imp2 == null)
                    return null;
                double min = imp.getDisplayRangeMin();
                double max = imp.getDisplayRangeMax();
                imp2.setDisplayRange(min, max);
            }
            if (imp2 == null)
                return null;
            if (imp2 != imp)
            {
                if (imp.isComposite())
                {
                    // imp2 = new CompositeImage(imp2, ((CompositeImage)imp).getMode());
                    // ((CompositeImage)imp2).copyLuts(imp);
                }
                else
                    imp2.setLut(imp.getProcessor().getLut());
            }
            imp2.setCalibration(imp.getCalibration());
            Calibration cal = imp2.getCalibration();
            if (cal.scaled()) cal.pixelDepth *= (double)imp.getNSlices() / imp2.getNSlices();
            object info = imp.getProperty("Info");
            if (info != null) imp2.setProperty("Info", info);
            imp2.setProperties(imp.getPropertiesAsArray());
            if (imp.isHyperStack())
                imp2.setOpenAsHyperStack(imp.isHyperStack());
            return imp2;
        }

        private ImagePlus resizeZ(ImagePlus imp, int newDepth, int interpolationMethod)
        {
            ImageStack stack1 = imp.getStack();
            int width = stack1.getWidth();
            int height = stack1.getHeight();
            int depth = stack1.getSize();
            int bitDepth = imp.getBitDepth();
            ImagePlus imp2 = IJ.createImage(imp.getTitle(), bitDepth + "-bit", width, height, newDepth);
            if (imp2 == null) return null;
            ImageStack stack2 = imp2.getStack();
            ImageProcessor ip = imp.getProcessor();
            ImageProcessor xzPlane1 = ip.createProcessor(width, depth);
            xzPlane1.setInterpolationMethod(interpolationMethod);
            ImageProcessor xzPlane2;
            object xzpixels1 = xzPlane1.getPixels();
            IJ.ShowStatus("Z Scaling...");
            for (int y = 0; y < height; y++)
            {
                IJ.showProgress(y, height - 1);
                for (int z = 0; z < depth; z++)
                { // get xz plane at y
                    object pixels1 = stack1.getPixels(z + 1);
                    Array.Copy((Array)pixels1, y * width, (Array)xzpixels1, z * width, width);
                }
                xzPlane2 = xzPlane1.resize(width, newDepth, averageWhenDownsizing);
                object xypixels2 = xzPlane2.getPixels();
                for (int z = 0; z < newDepth; z++)
                {
                    object pixels2 = stack2.getPixels(z + 1);
                    Array.Copy((Array)xypixels2, z * width, (Array)pixels2, y * width, width);
                }
            }
            return imp2;
        }


		public void setAverageWhenDownsizing(bool averageWhenDownsizing)
        {
            this.averageWhenDownsizing = averageWhenDownsizing;
        }
	}
}
